package middleware

import (
	context "context"
	"encoding/json"
	"errors"
	global "gitee.com/baal1990/epidemic/global"
	response "gitee.com/baal1990/epidemic/model/common/response_dfitc"
	"github.com/gin-gonic/gin"
	"strings"
	"time"
)

/*
针对Login服务的中间件

针对抵御抓包重放攻击 2019.10.17
同时使用timestamp和nonce的方案
1、nonce有客户端随机生成的一次性使用的字符串，每次请求带有该参数（可HASH(token + 序号 + 时间戳 + deviceNo) 等）。首次使用允许，第二次出现则判别为非法请求。
2、服务器端存储nonce（），如果发现已有，则为重复接收，认为是无效的（也可以允许重复次数）。
3、服务器存储nonce，可以用redis 或 map（重启会丢数据），有效期到后清空
4、时间戳作为参数，（传送时间戳参数，由时间戳和token 进行加密，这样可以防止时间戳被篡改）， 比如60秒有效。但是难以保证服务器和客户端的时钟是一致的。
5、时间戳有效范围内，比如24小时，如果时间戳超过该范围，则认为无效请求。
6、参数放置于POST请求头部，检测主要针对post请求/put请求
7、所以https本身就是防止重放攻击的，除非能复制socket，或者进行中间人攻击。
8、redis存储：key = nonce + user_id  ，value = timestamp
*/
type UriLimiter struct {
	Ref      int   `json:"ref"`
	UnixNano int64 `json:"unix_nano"`
}

const (
	REDIS_KEY_TOURIST_HASH         = "redis_key_tourist_hash" //游客设备号和token的映射关系
	REDIS_FIELD_SAFETY             = "safety"                 //安全记录域
	REDIS_KEY_APIFINGERPRINT_TIMES = "api_fingerprint_times_" //API数据包指纹和访问次数的记录，用于防止抓包重放攻击	fingerPrint
	REDIS_KEY_URI_LIMITER          = "uri_limiter_"           //key:uri_limiter_{ip}_{remote_ip}，field：uri, value:{ref:"",expire:UnixNano}，expire为过期的时间戳，ref为过期时间内的调用次数
)

var (
	ApiPackageMaxCnt   int     //重复接收API包次数上线
	ApiPackageKeepTime int     //API数据包指纹保存有效时间 单位秒
	SafetyCheckEnable  bool    //是否进行安全验证
	UriLimiterExpire   = 86400 //限制ip访问过期时间
	UriLimiterUriMax   = 100   //限制ip请求uri次数
)

// UriControl 安全校验，限制终端ip，防止暴力破解、短信攻击
func UriControl(expire int, count int) gin.HandlerFunc {
	if expire != 0 {
		UriLimiterExpire = expire
	}
	if count != 0 {
		UriLimiterUriMax = count
	}
	return func(c *gin.Context) {
		// 解析client-ip
		clientIp := c.ClientIP()
		// 解析deviceNo
		var err error
		deviceNo := c.GetHeader("deviceNo")
		if strings.HasSuffix(deviceNo, "==") { //如果手机号是加密的.
			//deviceNo, err = system.RsaDecrypt(deviceNo)
			deviceNoBt, err := global.AsymmetricCrypto.DecryptWithPrvKeyFromBase64(deviceNo)
			deviceNo = string(deviceNoBt)
			if err != nil {
				global.GVA_LOG.Sugar().Error("UriControl, RsaDecrypt fail", err)
				response.FailWithMessage("服务器内部错误", c)
				c.Abort()
				return
			}
		}
		uri := c.Request.RequestURI
		if clientIp == "" || deviceNo == "" {
			global.GVA_LOG.Sugar().Error("UriControl, invalid client-ip:", clientIp)
			response.FailWithMessage("参数错误", c)

			c.Abort()
			return
		}
		global.GVA_LOG.Sugar().Info("UriControl, client-ip:", clientIp, "deviceNo:", deviceNo, "URI:", uri)
		key := REDIS_KEY_URI_LIMITER + clientIp
		UriLimiter, err := GetUriLimiter(key, uri)
		if err != nil {
			global.GVA_LOG.Sugar().Info("UriControl, GetUriLimiter:", err, "key:", key, "field:", uri)
		}
		// 是否过期
		now := time.Now().UnixNano()
		global.GVA_LOG.Sugar().Info("UriControl, now:", now, "expire:", UriLimiter.UnixNano)
		if now <= UriLimiter.UnixNano { // 未过期
			if UriLimiter.Ref >= UriLimiterUriMax { // 超限
				global.GVA_LOG.Sugar().Info("UriControl, IP:", clientIp, "URI:", uri, "OUT OF LIMIT!!!")
				response.FailWithMessage("禁止访问", c)

				c.Abort()
				return
			} else { //未超限
				UriLimiter.Ref++
				if err := SetUriLimiter(UriLimiter, key, uri); err != nil {
					global.GVA_LOG.Sugar().Error("UriControl, SetUriLimiter:", err, "key:", key, "field:", uri, "value:", UriLimiter)
					response.FailWithMessage("服务器内部错误", c)

					c.Abort()
					return
				}
			}
		} else { // 过期
			UriLimiter.Ref = 1
			UriLimiter.UnixNano = time.Now().Add(time.Second * time.Duration(UriLimiterExpire)).UnixNano()
			if err := SetUriLimiter(UriLimiter, key, uri); err != nil {
				global.GVA_LOG.Sugar().Error("UriControl, SetUriLimiter:", err, "key:", key, "field:", uri, "value:", UriLimiter)
				response.FailWithMessage("服务器内部错误", c)

				c.Abort()
				return
			}
		}
		c.Next()
	}
}

// GetUriLimiter 读取UriLimiter
func GetUriLimiter(key, field string) (UriLimiter, error) {
	UriLimiter := UriLimiter{
		Ref:      0,
		UnixNano: 0,
	}
	//jsonBuf, err := redis.Bytes(system.Rdb.Do("HGET", key, field))
	jsonBuf, err := global.GVA_REDIS.Do(context.Background(), "HGET", key, field).Text()
	if err != nil {
		return UriLimiter, errors.New("HGET fail fail, " + err.Error())
	}
	if len(jsonBuf) != 0 {
		if err := json.Unmarshal([]byte(jsonBuf), &UriLimiter); err != nil {
			return UriLimiter, errors.New("easyjson.Unmarshal fail, " + err.Error())
		}
	}
	return UriLimiter, nil
}

// SetUriLimiter UriLimiter存入缓存
func SetUriLimiter(UriLimiter UriLimiter, key, field string) error {
	jsonBuf, err := json.Marshal(UriLimiter)
	if err != nil {
		return errors.New("easyjson.Marshal fail, " + err.Error())
	}
	if err := global.GVA_REDIS.Do(context.Background(), "HSET", key, field, jsonBuf).Err(); err != nil {
		return errors.New("HSET fail fail, " + err.Error())
	}
	return nil
}
